#include "as2_t3.h"
#include <stddef.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>

/* INCREMENT wordt inline omgezet in de berekening voor hoeveel het geheugen uitgebreid moet worden */
#define INCREMENT ((1<<alloc_size_power) * sizeof(mem_t))

/* het geheugen wordt opgedeeld in stukken die een task_t bevatten of een pointer naar een vrij stuk */
typedef union mem_union {
	union mem_union *next_free;
	task_t task;
} mem_t;

typedef mem_t *mem_ptr;
typedef char *empty_mem_ptr;
typedef long intptr_t;
/* first_free is een pointer naar het eerste vrije stukje geheugen */
mem_ptr first_free = NULL;

/* offset bepaalt hoe de mem_t's zijn uitgelijnd binnen het geheugen */
/* om te voorkomen dat er verkeerde pointers vrijgegeven worden */
intptr_t offset;

mem_ptr mem_first = NULL, mem_last;

/*
 * Deze functie zorgt voor een abstractielaag voor task_alloc, opdat er
 * altijd een nieuwe mem_t kan worden opgevraagd.
 *
 * Intern worden er exponentieel groeiend nieuwe stukken geheugen aangevraagd via brk()
 *
 * @return pointer naar een nieuwe mem_t
 */
mem_ptr mem_grow(void) {
	static empty_mem_ptr start = NULL, end;
	static int alloc_size_power;
	mem_ptr new_mem;
	if (start == NULL) {
		end = start = (empty_mem_ptr)sbrk(0);
		alloc_size_power = 1;
	}

	if (start == end) {
		if (sbrk(INCREMENT) == (void*) -1)
			return NULL;
		else
			end = (empty_mem_ptr)((int)end + (int)INCREMENT);

		alloc_size_power++;
	}

	 new_mem = (mem_ptr)start;
	start += sizeof(mem_t);

	return new_mem;
}


/*
 * Deze functie levert een pointer naar een task op.
 *
 */
void *task_alloc(void) {
	mem_ptr ptr;
	if (first_free == NULL) {
		mem_ptr new_mem = mem_grow();

		assert(new_mem->next_free == NULL); /* vers geheugen hoort alle bits uit te hebben uit veiligheidsoverwegingen */

		/* als mem_first=NULL, dan wordt deze functie voor het eerst in run-time aangeroepen en moet er nog e.e.a. ingesteld worden */
		if (mem_first == NULL) {
			mem_first = new_mem;
			offset = ((intptr_t)new_mem) % sizeof(mem_t);
		}

		first_free = new_mem;
		mem_last   = new_mem;
	}

	 ptr = first_free;
	first_free = first_free->next_free;

	return ptr;
}

/*
 * Deze functie geeft een task vrij.
 */
void task_free(void *ptr) {
	mem_ptr free_task = (mem_ptr)ptr;

	/* controleer of de pointer binnen het addresseerbare geheugen ligt en goed uitgelijnd is */
	if (free_task != NULL && free_task >= mem_first && free_task <= mem_last && (intptr_t)free_task%sizeof(mem_t) == offset) {
		/* voeg de task in op de eerste plek van de stack */
		free_task->next_free = first_free;
		first_free = free_task;
	}
}
